<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Java 基础语法 03：常用类库、集合与异常处理</title>
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f8f9fa; /* 浅灰背景 */
            color: #212529; /* 深色文字 */
            line-height: 1.6;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
        }
        .container {
            max-width: 800px;
            margin: 40px;
            padding: 30px;
            background-color: #ffffff; /* 白色内容区 */
            border-radius: 8px;
            box-shadow: 0 0 15px rgba(0,0,0,0.1);
        }
        h1, h2, h3, h4, h5, h6 {
            font-family: 'Merriweather', serif;
            color: #343a40;
            margin-top: 1.5em;
            margin-bottom: 0.8em;
        }
        h1 { font-size: 2.5em; }
        h2 { font-size: 2em; }
        h3 { font-size: 1.75em; }
        p {
            margin-bottom: 1em;
        }
        a {
            color: #3b82f6; /* 强调色：明亮蓝色 */
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
        pre {
            background-color: #e9ecef;
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto;
            font-family: 'IBM Plex Mono', monospace;
            font-size: 0.9em;
        }
        code {
            font-family: 'IBM Plex Mono', monospace;
            background-color: #e9ecef;
            padding: 0.2em 0.4em;
            border-radius: 3px;
        }
        pre code {
            padding: 0;
            background-color: transparent;
            border-radius: 0;
        }
        ul, ol {
            padding-left: 20px;
            margin-bottom: 1em;
        }
        li {
            margin-bottom: 0.5em;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 1.5em;
        }
        th, td {
            border: 1px solid #dee2e6;
            padding: 8px 12px;
            text-align: left;
        }
        th {
            background-color: #f1f3f5;
            font-weight: bold;
        }
        strong, b {
            font-weight: bold;
        }
        em, i {
            font-style: italic;
        }
    </style>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Merriweather:wght@400;700&family=IBM+Plex+Mono&display=swap" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-solarizedlight.min.css" rel="stylesheet" />
</head>
<body>
    <div class="container">
<h1>Java 基础语法 03：常用类库、集合与异常处理</h1>

<h2>1. 常用类库</h2>

<h3>1.1 StringBuilder 类</h3>

<p>当需要对字符串进行频繁修改（追加、插入、删除等）时，使用 <code>String</code> 类会产生很多临时的字符串对象，效率较低。<code>StringBuilder</code> 类则用于构建可变的字符序列，可以高效地进行字符串操作。</p>

<p><strong>主要特点：</strong></p>

<ul>
<li><strong>可变性：</strong> <code>StringBuilder</code> 对象的内容可以被修改，而不会创建新的对象。</li>
<li><strong>效率：</strong> 对于多次修改字符串的操作，<code>StringBuilder</code> 通常比 <code>String</code> 更高效。</li>
<li><strong>非线程安全：</strong> <code>StringBuilder</code> 不是线程安全的。如果在多线程环境中使用，应考虑使用 <code>StringBuffer</code>（线程安全，但效率略低）。</li>
</ul>

<p><strong>常用方法：</strong></p>

<ul>
<li><code>append(String str)</code>: 在末尾追加字符串。</li>
<li><code>insert(int offset, String str)</code>: 在指定位置插入字符串。</li>
<li><code>delete(int start, int end)</code>: 删除指定范围的字符。</li>
<li><code>replace(int start, int end, String str)</code>: 替换指定范围的字符。</li>
<li><code>reverse()</code>: 反转字符序列。</li>
<li><code>toString()</code>: 将 <code>StringBuilder</code> 对象转换为 <code>String</code> 对象。</li>
</ul>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");

        // 追加
        sb.append(" World");
        System.out.println(sb.toString()); // 输出: Hello World

        // 插入
        sb.insert(5, ", Java");
        System.out.println(sb.toString()); // 输出: Hello, Java World

        // 删除
        sb.delete(5, 11); // 删除 ", Java"
        System.out.println(sb.toString()); // 输出: HelloWorld

        // 替换
        sb.replace(5, 10, "Universe");
        System.out.println(sb.toString()); // 输出: HelloUniverse

        // 反转
        sb.reverse();
        System.out.println(sb.toString()); // 输出: esrevinUolleH
    }
}
</code></pre>

<h3>1.2 Math 类</h3>

<p><code>java.lang.Math</code> 类提供了许多用于执行基本数学运算的静态方法，如指数、对数、平方根以及三角函数等。</p>

<p><strong>常用方法：</strong></p>

<ul>
<li><code>abs(double a)</code>: 返回 <code>double</code> 值的绝对值。</li>
<li><code>sqrt(double a)</code>: 返回 <code>double</code> 值的正平方根。</li>
<li><code>pow(double a, double b)</code>: 返回第一个参数的第二个参数次幂的值。</li>
<li><code>max(double a, double b)</code>: 返回两个 <code>double</code> 值中较大的一个。</li>
<li><code>min(double a, double b)</code>: 返回两个 <code>double</code> 值中较小的一个。</li>
<li><code>random()</code>: 返回带正号的 <code>double</code> 值，该值大于等于 <code>0.0</code> 且小于 <code>1.0</code>。</li>
<li><code>round(float a)</code>: 返回最接近参数的 <code>int</code>。</li>
<li><code>ceil(double a)</code>: 返回最小的（最接近负无穷大）<code>double</code> 值，该值大于等于参数，并且等于某个整数。</li>
<li><code>floor(double a)</code>: 返回最大的（最接近正无穷大）<code>double</code> 值，该值小于等于参数，并且等于某个整数。</li>
</ul>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">public class MathExample {
    public static void main(String[] args) {
        System.out.println("绝对值: " + Math.abs(-10.5));       // 输出: 10.5
        System.out.println("平方根: " + Math.sqrt(16));         // 输出: 4.0
        System.out.println("幂运算: " + Math.pow(2, 3));        // 输出: 8.0
        System.out.println("最大值: " + Math.max(10, 20));       // 输出: 20.0
        System.out.println("最小值: " + Math.min(10, 20));       // 输出: 10.0
        System.out.println("随机数: " + Math.random());       // 输出: 0.0 到 1.0 之间的一个随机数
        System.out.println("四舍五入: " + Math.round(10.5f));    // 输出: 11
        System.out.println("向上取整: " + Math.ceil(10.1));     // 输出: 11.0
        System.out.println("向下取整: " + Math.floor(10.9));    // 输出: 10.0
    }
}
</code></pre>

<h3>1.3 Arrays 类</h3>

<p><code>java.util.Arrays</code> 类包含用于操作数组的各种静态方法（比如排序和搜索）。</p>

<p><strong>常用方法：</strong></p>

<ul>
<li><code>sort(int[] a)</code>: 对指定的 <code>int</code> 型数组按数字升序进行排序。</li>
<li><code>binarySearch(int[] a, int key)</code>: 使用二分搜索法来搜索指定的 <code>int</code> 型数组，以获得指定的值。数组必须事先按升序排序。</li>
<li><code>equals(int[] a, int[] a2)</code>: 如果两个指定的 <code>int</code> 型数组彼此相等，则返回 <code>true</code>。</li>
<li><code>fill(int[] a, int val)</code>: 将指定的 <code>int</code> 值分配给指定 <code>int</code> 型数组的每个元素。</li>
<li><code>toString(int[] a)</code>: 返回指定数组内容的字符串表示形式。</li>
<li><code>copyOf(T[] original, int newLength)</code>: 复制指定的数组，截取或用 <code>null</code>（如果为基本类型，则为默认值）填充，以使副本具有指定的长度。</li>
</ul>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">import java.util.Arrays;

public class ArraysExample {
    public static void main(String[] args) {
        int[] numbers = {5, 2, 8, 1, 9, 4};

        // 排序
        Arrays.sort(numbers);
        System.out.println("排序后: " + Arrays.toString(numbers)); // 输出: [1, 2, 4, 5, 8, 9]

        // 二分查找 (数组必须已排序)
        int index = Arrays.binarySearch(numbers, 8);
        System.out.println("元素 8 的索引: " + index); // 输出: 4

        // 比较数组
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {1, 2, 3};
        System.out.println("arr1 和 arr2 是否相等: " + Arrays.equals(arr1, arr2)); // 输出: true

        // 填充数组
        int[] filledArray = new int[5];
        Arrays.fill(filledArray, 10);
        System.out.println("填充后的数组: " + Arrays.toString(filledArray)); // 输出: [10, 10, 10, 10, 10]

        // 复制数组
        int[] copiedArray = Arrays.copyOf(numbers, 3);
        System.out.println("复制的数组: " + Arrays.toString(copiedArray)); // 输出: [1, 2, 4]
    }
}
</code></pre>

<h2>2. 集合框架 (Collections Framework)</h2>

<p>Java 集合框架提供了一套性能优良、使用方便的接口和类，用于存储和操作对象组。主要接口有 <code>List</code>、<code>Set</code> 和 <code>Map</code>。</p>

<h3>2.1 List 接口</h3>

<p><code>List</code> 是一个有序的集合（也称为序列）。允许包含重复的元素。可以通过索引来访问元素。</p>

<p><strong>常用实现类：</strong></p>

<ul>
<li><strong><code>ArrayList</code></strong>: 基于动态数组实现。查询快，增删慢（涉及元素移动）。线程不安全。</li>
<li><strong><code>LinkedList</code></strong>: 基于双向链表实现。增删快，查询慢。线程不安全。</li>
<li><strong><code>Vector</code></strong>: 与 <code>ArrayList</code> 类似，但它是线程安全的（方法被 <code>synchronized</code> 修饰），效率较低，已不常用。</li>
</ul>

<p><strong><code>ArrayList</code> 常用方法：</strong></p>

<ul>
<li><code>add(E e)</code>: 将指定的元素添加到此列表的尾部。</li>
<li><code>add(int index, E element)</code>: 在此列表中的指定位置插入指定的元素。</li>
<li><code>get(int index)</code>: 返回此列表中指定位置上的元素。</li>
<li><code>set(int index, E element)</code>: 用指定的元素替代此列表中指定位置上的元素。</li>
<li><code>remove(int index)</code>: 移除此列表中指定位置上的元素。</li>
<li><code>remove(Object o)</code>: 从此列表中移除指定元素的第一个匹配项（如果存在）。</li>
<li><code>size()</code>: 返回此列表中的元素数。</li>
<li><code>isEmpty()</code>: 如果此列表不包含任何元素，则返回 <code>true</code>。</li>
<li><code>contains(Object o)</code>: 如果此列表包含指定的元素，则返回 <code>true</code>。</li>
<li><code>clear()</code>: 从此列表中移除所有元素。</li>
</ul>

<p><strong><code>ArrayList</code> 示例与遍历：</strong></p>

<pre><code class="language-java">import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        List&lt;String&gt; names = new ArrayList&lt;&gt;();

        // 添加元素
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        names.add(1, "David"); // 在索引 1 处插入

        System.out.println("列表内容: " + names); // 输出: [Alice, David, Bob, Charlie]
        System.out.println("列表大小: " + names.size()); // 输出: 4
        System.out.println("获取索引为2的元素: " + names.get(2)); // 输出: Bob

        // 修改元素
        names.set(0, "Alicia");
        System.out.println("修改后列表内容: " + names); // 输出: [Alicia, David, Bob, Charlie]

        // 删除元素
        names.remove("Bob");
        System.out.println("删除 Bob 后: " + names); // 输出: [Alicia, David, Charlie]
        names.remove(0);
        System.out.println("删除索引 0 后: " + names); // 输出: [David, Charlie]

        // 遍历方式 1: 普通 for 循环 (通过索引)
        System.out.println("\n--- 普通 for 循环遍历 ---");
        for (int i = 0; i &lt; names.size(); i++) {
            System.out.println(names.get(i));
        }

        // 遍历方式 2: 增强 for 循环 (foreach)
        System.out.println("\n--- 增强 for 循环遍历 ---");
        for (String name : names) {
            System.out.println(name);
        }

        // 遍历方式 3: 迭代器 (Iterator)
        System.out.println("\n--- 迭代器遍历 ---");
        Iterator&lt;String&gt; iterator = names.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        // 遍历方式 4: Lambda 表达式 (Java 8+)
        System.out.println("\n--- Lambda 表达式遍历 ---");
        names.forEach(name -&gt; System.out.println(name));
        // 或者使用方法引用
        // names.forEach(System.out::println);

        System.out.println("列表是否为空: " + names.isEmpty()); // 输出: false
        System.out.println("是否包含 David: " + names.contains("David")); // 输出: true

        names.clear();
        System.out.println("清空后列表大小: " + names.size()); // 输出: 0
    }
}
</code></pre>

<h3>2.2 Set 接口</h3>

<p><code>Set</code> 是一个不包含重复元素的集合。它不保证集合中元素的顺序（但某些实现类可以保证）。</p>

<p><strong>常用实现类：</strong></p>

<ul>
<li><strong><code>HashSet</code></strong>: 基于哈希表实现。不保证元素的顺序，允许使用 <code>null</code> 元素。添加、删除、查找操作的平均时间复杂度为 O(1)。</li>
<li><strong><code>LinkedHashSet</code></strong>: <code>HashSet</code> 的子类，使用链表维护元素的插入顺序。</li>
<li><strong><code>TreeSet</code></strong>: 基于红黑树实现。元素会自动排序（自然排序或自定义比较器排序）。不允许 <code>null</code> 元素（除非自定义比较器允许）。</li>
</ul>

<p><strong><code>HashSet</code> 常用方法 (与 <code>List</code> 类似，但没有索引操作)：</strong></p>

<ul>
<li><code>add(E e)</code>: 如果指定元素尚不存在于此 set 中，则将其添加。</li>
<li><code>remove(Object o)</code>: 如果指定元素存在于此 set 中，则将其移除。</li>
<li><code>size()</code>: 返回此 set 中的元素数。</li>
<li><code>isEmpty()</code>: 如果此 set 不包含任何元素，则返回 <code>true</code>。</li>
<li><code>contains(Object o)</code>: 如果此 set 包含指定的元素，则返回 <code>true</code>。</li>
<li><code>clear()</code>: 从此 set 中移除所有元素。</li>
</ul>

<p><strong><code>HashSet</code> 示例与遍历：</strong></p>

<pre><code class="language-java">import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        Set&lt;String&gt; fruits = new HashSet&lt;&gt;();

        // 添加元素 (重复元素不会被添加)
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 这个不会被添加

        System.out.println("集合内容: " + fruits); // 输出顺序不确定，例如: [Orange, Apple, Banana]
        System.out.println("集合大小: " + fruits.size()); // 输出: 3

        // 删除元素
        fruits.remove("Banana");
        System.out.println("删除 Banana 后: " + fruits); // 输出: [Orange, Apple]

        System.out.println("是否包含 Orange: " + fruits.contains("Orange")); // 输出: true

        // 遍历方式 1: 增强 for 循环 (foreach)
        System.out.println("\n--- 增强 for 循环遍历 ---");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // 遍历方式 2: 迭代器 (Iterator)
        System.out.println("\n--- 迭代器遍历 ---");
        Iterator&lt;String&gt; iterator = fruits.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        // 遍历方式 3: Lambda 表达式 (Java 8+)
        System.out.println("\n--- Lambda 表达式遍历 ---");
        fruits.forEach(fruit -&gt; System.out.println(fruit));

        fruits.clear();
        System.out.println("清空后集合大小: " + fruits.size()); // 输出: 0
    }
}
</code></pre>

<h3>2.3 Map 接口</h3>

<p><code>Map</code> 用于存储键值对（key-value pairs）。键是唯一的，每个键最多映射到一个值。</p>

<p><strong>常用实现类：</strong></p>

<ul>
<li><strong><code>HashMap</code></strong>: 基于哈希表实现。不保证映射的顺序，允许使用 <code>null</code> 键和 <code>null</code> 值。是 <code>Map</code> 最常用的实现。</li>
<li><strong><code>LinkedHashMap</code></strong>: <code>HashMap</code> 的子类，使用链表维护键值对的插入顺序或访问顺序。</li>
<li><strong><code>TreeMap</code></strong>: 基于红黑树实现。键值对会根据键进行排序（自然排序或自定义比较器排序）。不允许 <code>null</code> 键（除非自定义比较器允许）。</li>
<li><strong><code>Hashtable</code></strong>: 与 <code>HashMap</code> 类似，但它是线程安全的，不允许 <code>null</code> 键和 <code>null</code> 值。效率较低，已不常用，推荐使用 <code>ConcurrentHashMap</code> 进行并发操作。</li>
</ul>

<p><strong><code>HashMap</code> 常用方法：</strong></p>

<ul>
<li><code>put(K key, V value)</code>: 将指定的值与此映射中的指定键关联。</li>
<li><code>get(Object key)</code>: 返回指定键所映射的值；如果此映射不包含该键的映射关系，则返回 <code>null</code>。</li>
<li><code>remove(Object key)</code>: 如果存在一个键的映射关系，则将其从此映射中移除。</li>
<li><code>containsKey(Object key)</code>: 如果此映射包含指定键的映射关系，则返回 <code>true</code>。</li>
<li><code>containsValue(Object value)</code>: 如果此映射将一个或多个键映射到指定值，则返回 <code>true</code>。</li>
<li><code>size()</code>: 返回此映射中的键-值映射关系数。</li>
<li><code>isEmpty()</code>: 如果此映射未包含键-值映射关系，则返回 <code>true</code>。</li>
<li><code>clear()</code>: 从此映射中移除所有映射关系。</li>
<li><code>keySet()</code>: 返回此映射中所包含的键的 <code>Set</code> 视图。</li>
<li><code>values()</code>: 返回此映射中所包含的值的 <code>Collection</code> 视图。</li>
<li><code>entrySet()</code>: 返回此映射中所包含的映射关系的 <code>Set</code> 视图（每个元素是 <code>Map.Entry&lt;K, V&gt;</code> 对象）。</li>
</ul>

<p><strong><code>HashMap</code> 示例与遍历：</strong></p>

<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;

public class HashMapExample {
    public static void main(String[] args) {
        Map&lt;String, Integer&gt; studentScores = new HashMap&lt;&gt;();

        // 添加键值对
        studentScores.put("Alice", 90);
        studentScores.put("Bob", 85);
        studentScores.put("Charlie", 95);
        studentScores.put("David", 85);
        studentScores.put("Alice", 92); // 会覆盖之前的 Alice 的值

        System.out.println("Map 内容: " + studentScores); // 输出顺序不确定
        System.out.println("Map 大小: " + studentScores.size()); // 输出: 4

        // 获取值
        System.out.println("Bob 的分数: " + studentScores.get("Bob")); // 输出: 85
        System.out.println("Eve 的分数: " + studentScores.get("Eve")); // 输出: null (因为 Eve 不存在)

        // 删除键值对
        studentScores.remove("David");
        System.out.println("删除 David 后: " + studentScores);

        System.out.println("是否包含键 Charlie: " + studentScores.containsKey("Charlie")); // 输出: true
        System.out.println("是否包含值 90: " + studentScores.containsValue(90)); // 输出: false (Alice 的值已更新为 92)

        // 遍历方式 1: 遍历键集合 (keySet)
        System.out.println("\n--- 遍历键集合 (keySet) ---");
        Set&lt;String&gt; keys = studentScores.keySet();
        for (String key : keys) {
            System.out.println("键: " + key + ", 值: " + studentScores.get(key));
        }

        // 遍历方式 2: 遍历 Entry 集合 (entrySet)
        System.out.println("\n--- 遍历 Entry 集合 (entrySet) ---");
        Set&lt;Map.Entry&lt;String, Integer&gt;&gt; entries = studentScores.entrySet();
        for (Map.Entry&lt;String, Integer&gt; entry : entries) {
            System.out.println("键: " + entry.getKey() + ", 值: " + entry.getValue());
        }

        // 遍历方式 3: 使用迭代器遍历 Entry 集合
        System.out.println("\n--- 迭代器遍历 Entry 集合 ---");
        Iterator&lt;Map.Entry&lt;String, Integer&gt;&gt; iterator = studentScores.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry&lt;String, Integer&gt; entry = iterator.next();
            System.out.println("键: " + entry.getKey() + ", 值: " + entry.getValue());
        }

        // 遍历方式 4: Lambda 表达式 (Java 8+)
        System.out.println("\n--- Lambda 表达式遍历 ---");
        studentScores.forEach((key, value) -&gt; {
            System.out.println("键: " + key + ", 值: " + value);
        });

        studentScores.clear();
        System.out.println("清空后 Map 大小: " + studentScores.size()); // 输出: 0
    }
}
</code></pre>

<h2>3. 异常处理 (Exception Handling)</h2>

<p>异常是在程序执行期间发生的事件，它中断了程序的正常指令流。Java 提供了强大的异常处理机制，允许程序捕获和处理这些错误，而不是直接崩溃。</p>

<p><strong>异常的层次结构：</strong></p>

<p>所有异常类都继承自 <code>java.lang.Throwable</code> 类。
<code>Throwable</code> 有两个主要的子类：</p>

<ul>
<li><strong><code>Error</code></strong>: 表示编译时和系统错误，通常是程序无法处理的严重问题（例如，<code>OutOfMemoryError</code>, <code>StackOverflowError</code>）。应用程序通常不应该捕获 <code>Error</code>。</li>
<li><strong><code>Exception</code></strong>: 表示程序本身可以处理的异常。它又分为两大类：
    <ul>
    <li><strong>Checked Exceptions (受检异常)</strong>: 在编译时必须处理的异常。如果一个方法可能抛出受检异常，那么它必须通过 <code>throws</code> 关键字声明该异常，或者在方法内部使用 <code>try-catch</code> 块来捕获它。例如 <code>IOException</code>, <code>SQLException</code>。</li>
    <li><strong>Unchecked Exceptions (非受检异常)</strong>: 也称为运行时异常 (<code>RuntimeException</code> 及其子类)。这些异常在编译时不强制要求处理，通常是由程序逻辑错误引起的。例如 <code>NullPointerException</code>, <code>ArrayIndexOutOfBoundsException</code>, <code>ArithmeticException</code>。</li>
    </ul>
</li>
</ul>

<h3>3.1 <code>try-catch-finally</code> 语句</h3>

<p>这是 Java 中处理异常的核心机制。</p>

<ul>
<li><strong><code>try</code> 块</strong>: 包含可能会抛出异常的代码。</li>
<li><strong><code>catch</code> 块</strong>: 用于捕获并处理特定类型的异常。一个 <code>try</code> 块可以有多个 <code>catch</code> 块，用于处理不同类型的异常。<code>catch</code> 块的顺序很重要，子类异常应该在父类异常之前捕获。</li>
<li><strong><code>finally</code> 块</strong>: 无论是否发生异常，<code>finally</code> 块中的代码总是会被执行（除非 JVM 退出）。通常用于释放资源，如关闭文件流、数据库连接等。</li>
</ul>

<p><strong>语法：</strong></p>

<pre><code class="language-java">try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
} finally {
    // 总是会执行的代码 (可选)
}
</code></pre>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">public class TryCatchFinallyExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("结果: " + result); // 这行不会执行
        } catch (ArithmeticException e) {
            System.err.println("捕获到算术异常: " + e.getMessage());
            // e.printStackTrace(); // 打印详细的堆栈跟踪信息
        } catch (Exception e) { // 可以捕获更通用的异常
            System.err.println("捕获到其他异常: " + e.getMessage());
        } finally {
            System.out.println("Finally 块总是会执行。");
        }

        System.out.println("程序继续执行...");
    }

    public static int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return a / b;
    }
}
</code></pre>

<p><strong>输出：</strong></p>

<pre><code>捕获到算术异常: 除数不能为零
Finally 块总是会执行。
程序继续执行...
</code></pre>

<h3>3.2 <code>throws</code> 关键字</h3>

<p>如果一个方法可能会抛出受检异常，但它本身不处理这个异常，那么它必须使用 <code>throws</code> 关键字在方法签名中声明这个异常，将异常处理的责任交给调用者。</p>

<p><strong>语法：</strong></p>

<pre><code class="language-java">public void myMethod() throws ExceptionType1, ExceptionType2 {
    // 可能抛出 ExceptionType1 或 ExceptionType2 的代码
}
</code></pre>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">import java.io.IOException;
import java.io.FileReader;

public class ThrowsExample {

    // readFile 方法声明了它可能抛出 IOException
    public static void readFile(String filePath) throws IOException {
        FileReader reader = null;
        try {
            reader = new FileReader(filePath);
            char[] buffer = new char[1024];
            int bytesRead = reader.read(buffer);
            System.out.println("读取内容: " + new String(buffer, 0, bytesRead));
        } finally {
            if (reader != null) {
                try {
                    reader.close(); // close 方法也可能抛出 IOException
                } catch (IOException e) {
                    System.err.println("关闭文件时出错: " + e.getMessage());
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            readFile("non_existent_file.txt");
        } catch (IOException e) {
            System.err.println("main 方法中捕获到 IO 异常: " + e.getMessage());
        }
    }
}
</code></pre>

<h3>3.3 <code>throw</code> 关键字</h3>

<p><code>throw</code> 关键字用于在代码中显式地抛出一个异常对象。通常用于在检测到错误条件时主动中断程序流程。</p>

<p><strong>示例（在上面的 <code>divide</code> 方法中已使用）：</strong></p>

<pre><code class="language-java">if (b == 0) {
    throw new ArithmeticException("除数不能为零");
}
</code></pre>

<h3>3.4 自定义异常</h3>

<p>有时，Java 内置的异常类可能不足以描述应用程序特有的错误情况。这时可以创建自定义异常类。
自定义异常类通常继承自 <code>Exception</code> (用于受检异常) 或 <code>RuntimeException</code> (用于非受检异常)。</p>

<p><strong>创建自定义异常的步骤：</strong></p>

<ol>
<li>创建一个类，继承自 <code>Exception</code> 或其子类。</li>
<li>通常提供至少两个构造方法：
    <ul>
    <li>一个无参构造方法。</li>
    <li>一个带有字符串参数的构造方法，用于传递错误信息（通常调用父类的相应构造方法 <code>super(message)</code>）。</li>
    </ul>
</li>
</ol>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">// 自定义受检异常
class InsufficientFundsException extends Exception {
    public InsufficientFundsException() {
        super("账户余额不足");
    }

    public InsufficientFundsException(String message) {
        super(message);
    }
}

// 自定义非受检异常
class InvalidAgeException extends RuntimeException {
    public InvalidAgeException() {
        super("无效的年龄");
    }

    public InvalidAgeException(String message) {
        super(message);
    }
}

class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount &lt;= 0) {
            throw new IllegalArgumentException("存款金额必须为正数");
        }
        balance += amount;
        System.out.println("成功存款: " + amount + ", 当前余额: " + balance);
    }

    // 声明抛出自定义受检异常
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount &lt;= 0) {
            throw new IllegalArgumentException("取款金额必须为正数");
        }
        if (amount &gt; balance) {
            throw new InsufficientFundsException("尝试取款 " + amount + ", 但余额仅为 " + balance);
        }
        balance -= amount;
        System.out.println("成功取款: " + amount + ", 当前余额: " + balance);
    }

    public double getBalance() {
        return balance;
    }
}

public class CustomExceptionExample {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        try {
            account.deposit(500);
            account.withdraw(200);
            // account.withdraw(1500); // 这会抛出 InsufficientFundsException
            account.deposit(-100); // 这会抛出 IllegalArgumentException (非受检)
        } catch (InsufficientFundsException e) {
            System.err.println("银行操作错误: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.err.println("参数错误: " + e.getMessage());
        } finally {
            System.out.println("银行操作结束，最终余额: " + account.getBalance());
        }

        try {
            setAge(-5);
        } catch (InvalidAgeException e) {
            System.err.println("年龄设置错误: " + e.getMessage());
        }
    }

    public static void setAge(int age) {
        if (age &lt; 0 || age &gt; 150) {
            throw new InvalidAgeException("年龄必须在 0 到 150 之间，输入值为: " + age);
        }
        System.out.println("年龄设置为: " + age);
    }
}
</code></pre>

<p><strong>异常处理的最佳实践：</strong></p>

<ul>
<li><strong>具体捕获：</strong> 尽量捕获最具体的异常类，而不是直接捕获 <code>Exception</code> 或 <code>Throwable</code>。</li>
<li><strong>不要忽略异常：</strong> 空的 <code>catch</code> 块是不好的做法，至少应该记录异常信息。</li>
<li><strong>资源清理：</strong> 使用 <code>finally</code> 块或 <code>try-with-resources</code> 语句（Java 7+）来确保资源（如文件流、数据库连接）被正确关闭。</li>
<li><strong>不要用异常控制流程：</strong> 异常处理机制的开销相对较大，不应该用作正常的程序流程控制。</li>
<li><strong>提供有用的错误信息：</strong> 异常信息应该清晰明了，有助于定位问题。</li>
<li><strong>适时抛出异常：</strong> 当方法无法处理某个错误条件时，应该抛出异常通知调用者。</li>
</ul>

<h2>4. 文件与 IO (输入/输出)</h2>

<p>Java 的 <code>java.io</code> 包提供了进行输入输出操作的类。文件操作是 IO 操作中最常见的一种。本节主要介绍如何进行文本文件的读写。</p>

<h3>4.1 File 类</h3>

<p><code>java.io.File</code> 类是文件和目录路径名的抽象表示。它不直接用于读写文件内容，而是用于操作文件和目录的属性，如创建、删除、重命名、获取路径、判断类型等。</p>

<p><strong>常用方法：</strong></p>

<ul>
<li><code>exists()</code>: 测试此抽象路径名表示的文件或目录是否存在。</li>
<li><code>isFile()</code>: 测试此抽象路径名表示的文件是否是一个标准文件。</li>
<li><code>isDirectory()</code>: 测试此抽象路径名表示的文件是否是一个目录。</li>
<li><code>getName()</code>: 返回由此抽象路径名表示的文件或目录的名称。</li>
<li><code>getPath()</code>: 将此抽象路径名转换为一个路径名字符串。</li>
<li><code>getAbsolutePath()</code>: 返回此抽象路径名的绝对路径名字符串。</li>
<li><code>getParent()</code>: 返回此抽象路径名的父路径名的路径名字符串，如果此路径名没有指定父目录，则返回 <code>null</code>。</li>
<li><code>mkdir()</code>: 创建此抽象路径名指定的目录。</li>
<li><code>mkdirs()</code>: 创建此抽象路径名指定的目录，包括所有必需但不存在的父目录。</li>
<li><code>createNewFile()</code>: 当且仅当不存在具有此抽象路径名指定名称的文件时，原子地创建一个由此抽象路径名指定的新空文件。</li>
<li><code>delete()</code>: 删除此抽象路径名表示的文件或目录。</li>
<li><code>length()</code>: 返回由此抽象路径名表示的文件的长度。</li>
<li><code>listFiles()</code>: 返回一个抽象路径名数组，这些路径名表示此抽象路径名表示的目录中的文件。</li>
</ul>

<p><strong>示例代码：</strong></p>

<pre><code class="language-java">import java.io.File;
import java.io.IOException;

public class FileExample {
    public static void main(String[] args) {
        File file = new File("example.txt");
        System.out.println("文件是否存在: " + file.exists());

        try {
            if (!file.exists()) {
                file.createNewFile();
                System.out.println("文件创建成功: " + file.getName());
            }
        } catch (IOException e) {
            System.err.println("创建文件时出错: " + e.getMessage());
        }

        System.out.println("是否是文件: " + file.isFile());
        System.out.println("绝对路径: " + file.getAbsolutePath());

        File dir = new File("my_directory");
        if (!dir.exists()) {
            dir.mkdir();
            System.out.println("目录创建成功: " + dir.getName());
        }

        // 清理
        if (file.exists()) file.delete();
        if (dir.exists()) dir.delete();
    }
}
</code></pre>

<h3>4.2 IO 流概述</h3>

<p>Java 中的 IO 操作是通过流 (Stream) 来实现的。流是一个连续的数据序列，可以从中读取数据（输入流）或向其中写入数据（输出流）。</p>

<p>根据处理的数据单元不同，流可以分为：</p>

<ul>
<li><strong>字节流 (Byte Streams)</strong>: 以字节 (8位) 为单位处理数据。主要基类是 <code>InputStream</code> 和 <code>OutputStream</code>。适用于处理二进制文件（如图片、音频、视频）或任何类型的文件。</li>
<li><strong>字符流 (Character Streams)</strong>: 以字符 (16位 Unicode) 为单位处理数据。主要基类是 <code>Reader</code> 和 <code>Writer</code>。专为处理文本数据设计，会自动处理字符编码。</li>
</ul>

<h3>4.3 字符流进行文本文件读写</h3>

<p>对于文本文件的读写，使用字符流更为方便。</p>

<h4>4.3.1 FileWriter 和 FileReader</h4>

<ul>
<li><code>FileWriter</code>: 用于将字符写入文件。</li>
<li><code>FileReader</code>: 用于从文件读取字符。</li>
</ul>

<p><strong>示例：写入文本文件</strong></p>

<pre><code class="language-java">import java.io.FileWriter;
import java.io.IOException;

public class SimpleFileWriterExample {
    public static void main(String[] args) {
        String filePath = "output.txt";
        try (FileWriter writer = new FileWriter(filePath)) { // try-with-resources 自动关闭流
            writer.write("Hello, Java IO!\n");
            writer.write("这是第二行内容。");
            System.out.println("数据已成功写入到 " + filePath);
        } catch (IOException e) {
            System.err.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}
</code></pre>

<p><strong>示例：读取文本文件</strong></p>

<pre><code class="language-java">import java.io.FileReader;
import java.io.IOException;

public class SimpleFileReaderExample {
    public static void main(String[] args) {
        String filePath = "output.txt"; // 假设 output.txt 已存在且有内容
        try (FileReader reader = new FileReader(filePath)) {
            int character;
            System.out.println("从 " + filePath + " 读取内容:");
            while ((character = reader.read()) != -1) { // read() 返回 -1 表示到达文件末尾
                System.out.print((char) character);
            }
            System.out.println();
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}
</code></pre>

<h4>4.3.2 BufferedWriter 和 BufferedReader</h4>

<p>直接使用 <code>FileWriter</code> 和 <code>FileReader</code> 进行读写时，每次操作都会直接访问磁盘，效率较低。<code>BufferedWriter</code> 和 <code>BufferedReader</code> 提供了缓冲机制，可以显著提高读写性能。</p>

<ul>
<li><code>BufferedWriter</code>: 将文本写入字符输出流，缓冲各个字符，从而提供单个字符、数组和字符串的高效写入。</li>
<li><code>BufferedReader</code>: 从字符输入流中读取文本，缓冲各个字符，从而实现字符、数组和行的高效读取。</li>
</ul>

<p><strong>示例：使用 BufferedWriter 写入文本文件</strong></p>

<pre><code class="language-java">import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedFileWriterExample {
    public static void main(String[] args) {
        String filePath = "buffered_output.txt";
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("使用 BufferedWriter 写入数据。");
            writer.newLine(); // 写入一个平台相关的换行符
            writer.write("这样效率更高。");
            System.out.println("数据已成功写入到 " + filePath);
        } catch (IOException e) {
            System.err.println("写入文件时发生错误: " + e.getMessage());
        }
    }
}
</code></pre>

<p><strong>示例：使用 BufferedReader 读取文本文件</strong></p>

<pre><code class="language-java">import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedFileReaderExample {
    public static void main(String[] args) {
        String filePath = "buffered_output.txt"; // 假设 buffered_output.txt 已存在
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            System.out.println("从 " + filePath + " 读取内容:");
            while ((line = reader.readLine()) != null) { // readLine() 一次读取一行
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}
</code></pre>

<h3>4.4 <code>try-with-resources</code> 语句</h3>

<p>在 Java 7 及更高版本中，<code>try-with-resources</code> 语句是一种 <code>try</code> 语句，它声明一个或多个资源。资源是指在程序完成后必须关闭的对象（例如文件流、数据库连接等）。<code>try-with-resources</code> 语句确保在语句结束时关闭每个资源。</p>

<p>任何实现了 <code>java.lang.AutoCloseable</code> 接口（包括所有实现了 <code>java.io.Closeable</code> 的对象）的对象都可以用作资源。</p>

<p><strong>语法：</strong></p>

<pre><code class="language-java">try (ResourceType resource1 = initialization1; 
     ResourceType resource2 = initialization2) {
    // 使用资源
} catch (ExceptionType e) {
    // 处理异常
}
// finally 块是可选的，如果需要，可以添加
// 资源会自动关闭，无论 try 块是正常完成还是由于异常而突然完成
</code></pre>

<p>前面的 <code>FileWriter</code>, <code>FileReader</code>, <code>BufferedWriter</code>, <code>BufferedReader</code> 示例都使用了 <code>try-with-resources</code> 语句，这使得代码更简洁，并避免了忘记关闭流而导致的资源泄漏问题。</p>

<h2>总结</h2>

<p>本章我们学习了 Java 中几个非常重要的常用类库：<code>StringBuilder</code> 用于高效的字符串操作，<code>Math</code> 提供了数学计算功能，<code>Arrays</code> 简化了数组操作。接着，我们深入探讨了 Java 集合框架的核心接口 <code>List</code>、<code>Set</code> 和 <code>Map</code>，并重点学习了它们常用的实现类 <code>ArrayList</code>、<code>HashSet</code> 和 <code>HashMap</code>，以及各种遍历集合的方法。最后，我们详细介绍了 Java 的异常处理机制，包括 <code>try-catch-finally</code> 语句、<code>throws</code> 和 <code>throw</code> 关键字，以及如何创建和使用自定义异常。掌握这些内容对于编写健壮、高效的 Java 程序至关重要。</p>

    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
    <script>
        Prism.plugins.autoloader.languages_path = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/';
    </script>
</body>
</html>